/**
 * Copyright 2006 gworks.com.au
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. (http://www.apache.org/licenses/LICENSE-2.0)
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for 
 * the specific language governing permissions and limitations under the License.
 *
 * <dhamma-message src="Atisha (11th century Tibetan Buddhist master)">
 * 	The greatest achievement is selflessness. The greatest worth is self-mastery.
 * 	The greatest quality is seeking to serve others. The greatest precept is continual awareness.
 * 	The greatest medicine is the emptiness of everything. The greatest action is not conforming with the worlds ways.
 * 	The greatest magic is transmuting the passions. The greatest generosity is non-attachment.
 * 	The greatest goodness is a peaceful mind. The greatest patience is humility.
 * 	The greatest effort is not concerned with results. The greatest meditation is a mind that lets go.
 * 	The greatest wisdom is seeing through appearances. 
 * </dhamma-message>
 */
package org.javaongems.client;

import java.util.HashMap;
import java.util.Map;

import org.javaongems.client.forms.Form;
import org.javaongems.client.forms.FormLoadStatusCallback;
import org.javaongems.client.rt.io.AsyncCallbackFilter;
import org.javaongems.client.rt.io.HandleFailuresCallback;
import org.javaongems.client.service.HttpPostRpc;
import org.javaongems.client.service.HttpPostRpcAsync;
import org.javaongems.client.service.ResourceBundleRpc;
import org.javaongems.client.service.ResourceBundleRpcAsync;
import org.javaongems.client.service.ResourceBundleRpc.LocaleInfo;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.ServiceDefTarget;

/**
 * <p>Provides a common template to reduce the overhead of creating a Gemlet</p>
 * <p>Responsibilities:
 * <ul>
 * 	<li> cache service
 * 	<li> prepare resource bundle service
 * 	<li> link getGemProperty() method to javascript handle 
 * 	<li> wire up ApplCtx and Gem Id to entry point bootstrapped data
 * 	<li> query a resource bundle
 * 	<li> decorate Async callback with common application scaffoldings
 * </ul>
 * </p>
 * <p>Collaborators:
 * <ul>
 * 	<li> {@link org.javaongems.server.EntryPointPage} - prepares the entry point that is consumed by the gemlet
 * </ul>
 * </p>
 * <p>Constraints:
 * <ul>
 * 	<li> GWT emulation subsystem
 * 	<li> GWT compiler does not support Java generics
 * </ul>
 * </p>
 * 
 * @see Gemlet
 * @author Ashin Wimalajeewa (ash)
 */
public abstract class AbstractGemlet implements Gemlet {
	final static String GEM_BUNDLES_CARGO = "gem-bundles";
	final public String GEM_CARGO_CALLBACK_ARGS = "gem-callback-args";
		
	protected boolean isDebug = false;
	protected String applCtx;
	protected String gemsGwtRpcCtx;
	protected HashMap services = new HashMap();
	protected HashMap bundles = new HashMap();

	public AbstractGemlet() {
		Gem.stone.getCargo().put(GEM_BUNDLES_CARGO, bundles);
	}
	
	/**
	 * An implementation of the open service method which supports:
	 * <ul>
	 * 	<li> opening the resource bundle service
	 * </ul>
	 * 
	 * @param serviceName - logical name of the service registered under the
	 * GemServe servlet
	 * 
	 * @see ResourceBundleRpc
	 * @see ResourceBundleRpc#SERVICE_CTX - for the service name handle
	 * @see org.javaongems.client.Gemlet#openService(java.lang.String)
	 */
	public ServiceDefTarget openService(String serviceName) {
		ServiceDefTarget ret = getServiceFromCache(serviceName);
		if (ret != null)
			return ret;
		if (ResourceBundleRpc.SERVICE_CTX.equals(serviceName))
			ret = (ServiceDefTarget) initialiseResourceBundleService();
		else if (HttpPostRpc.DEFAULT_SERVICE_CTX.equals(serviceName))
			ret = (ServiceDefTarget) initialiseHttpPostPickupService(HttpPostRpc.DEFAULT_SERVICE_CTX);
		return ret;
	}
	
	/**
	 * Query that returns the fully Gems RPC context with the service name.
	 * @param serviceName - the logical name of the service
	 * @return getApplCtx() + getGemsGwtRpcContext() + serviceName
	 */
	public String getEndpointForService(String serviceName) {
		return getApplCtx() + getGemsGwtRpcContext() + serviceName;
	}

	/**
	 * An implementation of get application context method which uses the value
	 * bootstrapped to the entry point's gem properties data.
	 * 
	 * @see Gemlet#getGemProperty(String) - key: "gem.app.ctx"
	 * @see org.javaongems.client.Gemlet#getApplCtx()
	 */
	public String getApplCtx() {
		if (applCtx == null) 
			applCtx = Gem.stone.getGemPropertyAsString(Gemlet.GEM_APP_CTX, "");
		return applCtx;
	}

	/**
	 * An implementation of get gem id method which uses the value
	 * bootstrapped to the entry point's gem properties data.
	 * 
	 * @see Gemlet#getGemProperty(String) - key: "gem.id"
	 * @see org.javaongems.client.Gemlet#getGemId()
	 */
	public String getGemId() {
		return (String) getGemProperty(Gemlet.GEM_ID);
	}
	
	/**
	 * An implementation of the get gem property method which uses the javascript
	 * method handle specified by the {@link org.javaongems.server.EntryPointPage} creator.
	 * 
	 * @see org.javaongems.server.EntryPointPage#HTML_JS_GEM_PROP_FUNC
	 * @see org.javaongems.client.Gemlet#getGemProperty(java.lang.String)
	 */
	native public Object getGemProperty(String prop) /*-{
		return $wnd.getGemProperty(prop);
	}-*/;
	
	/** 
	 * An implementation of the get resource bundle method that uses the resource 
	 * bundle service. Note, because this method is service based, it will return
	 * null for the first request of a given baseName and locale combination until
	 * the service result has returned and been added to the cache. Therefore this 
	 * method collaborates with the {@link #onResourceBundleFetched(String, LocaleInfo, Map)}
	 * and the {@link ResourceBundleCallback} class.
	 * 
	 * @see org.javaongems.client.Gemlet#getResourceBundle(java.lang.String, org.javaongems.client.service.ResourceBundleRpc.LocaleInfo)
	 */
	public Map getResourceBundle(String baseName, LocaleInfo locale) {
		String bundleKey = toBundleKey(baseName, locale);
		Map ret = (Map) bundles.get(bundleKey);
		if (ret == null) {
			ResourceBundleRpcAsync service = (ResourceBundleRpcAsync) openService(ResourceBundleRpc.SERVICE_CTX);
			ResourceBundleCallback callback = new ResourceBundleCallback(baseName, locale);
			service.getResourceBundle(baseName, locale, callback);
		}
		return ret;
	}
	
	/** 
	 * An implementation of the decorate callback method which decorates each async call  
	 * back with failure and form loading decorations. 
	 * 
	 * @param args - If not null and and the first argument is a {@link Form} object, 
	 * then that form object will be set for the form loading decoration.  
	 */
	public AsyncCallbackFilter decorateCallback(AsyncCallback toDecorate, Object[] args) {
		HandleFailuresCallback failuresWrapper = new HandleFailuresCallback(toDecorate);
		FormLoadStatusCallback formWrapper = new FormLoadStatusCallback(null, failuresWrapper);
		if (args != null && args.length > 0 ) {
			if (args[0] instanceof Form)
				formWrapper.setForm((Form)args[0]);
		}
		return formWrapper;
	}
	
	/**
	 * Query to determine the Gems gwt rpc context which was bootstrapped to 
	 * the entry points' page by the {@link org.javaongems.server.EntryPointPage} object. This value would be
	 * originally derived from the web.xml file url-mapping value for the
	 * GemServe servlet.
	 * 
	 * @see org.javaongems.server.EntryPointPage
	 * @see Gemlet#getGemProperty(String) - key: "gem.gwt.rpc.ctx"
	 * @return the gems gwt rpc context for all services
	 */
	protected String getGemsGwtRpcContext() {
		if (gemsGwtRpcCtx == null)
			gemsGwtRpcCtx = Gem.stone.getGemPropertyAsString(GEM_GWT_RPC_CTX, "invalid");
		return gemsGwtRpcCtx;
	}
	
	/**
	 * Query the service directly from the cache
	 * @param serviceName - logical name of the service registered under the
	 * GemServe servlet
	 * @return a async rpc service for the service name
	 */
	protected ServiceDefTarget getServiceFromCache(String serviceName) {
		ServiceDefTarget ret = (ServiceDefTarget) services.get(serviceName);
		return ret;
	}
	
	/**
	 * Command that sets the service proxy in the cache
	 * @param serviceName - logical name of the service registered under the
	 * GemServe servlet
	 * @param service - the service proxy to cache
	 */
	protected void setServiceInCache(String serviceName, ServiceDefTarget service) {
		services.put(serviceName, service);
	}
	
	/**
	 * Prepares the resource bundle service proxy for subsequent invocation. 
	 * @return the resource bundle service
	 * @see #openService(String)
	 */
	protected ResourceBundleRpcAsync initialiseResourceBundleService() {
		ServiceDefTarget sdt = (ServiceDefTarget) GWT.create(ResourceBundleRpc.class);
	    String ctx = getEndpointForService(ResourceBundleRpc.SERVICE_CTX);
	    sdt.setServiceEntryPoint(ctx);
	    setServiceInCache(ResourceBundleRpc.SERVICE_CTX, sdt);
		return (ResourceBundleRpcAsync) sdt;
	}

	/**
	 * Prepares the http post pickup service proxy for subsequent invocation.
	 * Note, that there can be many different implementations for this service.
	 * Each one should be available under a different context.
	 * @param serviceCtx - the service name for the http post service
	 * @return the http post pickup service
	 * @see #openService(String)
	 */
	protected HttpPostRpcAsync initialiseHttpPostPickupService(String serviceCtx) {
		ServiceDefTarget sdt = (ServiceDefTarget) GWT.create(HttpPostRpc.class);
	    String ctx = getEndpointForService(serviceCtx);
	    sdt.setServiceEntryPoint(ctx);
	    setServiceInCache(serviceCtx, sdt);
		return (HttpPostRpcAsync) sdt;
	}
	
	/**
	 * Event handler that is invoked when a requested resource bundle has been fetched.
	 * @param baseName - basename of the resource bundle requested
	 * @param locale - locale of the resource bundle requested
	 * @param bundle - the bundle that was fetched from the service
	 */
	protected void onResourceBundleFetched(String baseName, LocaleInfo locale, Map bundle) {
		// nothing to do
	}
	
	/**
	 * <p>Responsibilities:
	 * <ul>
	 * 	<li> callback handler for the get resource bundle service requests
	 * </ul>
	 * </p>
	 * <p>Collaborators:
	 * <ul>
	 * 	<li> {@link AbstractGemlet#getResourceBundle(String, LocaleInfo)} - invoker
	 * 	<li> {@link AbstractGemlet#onResourceBundleFetched(String, LocaleInfo, Map)} - receiver
	 * </ul>
	 * </p>
	 */
	protected class ResourceBundleCallback implements AsyncCallback {
		private String name;
		private LocaleInfo locale;
		
		public ResourceBundleCallback(String nm, LocaleInfo info) {
			name = nm;
			locale = info;
		}
		
		public void onFailure(Throwable caught) {
			Gem.stone.handleServiceFailures(caught);
		}

		public void onSuccess(Object result) {
			Map ret = (Map) result;
			HashMap map = (HashMap) Gem.stone.getCargo().get(GEM_BUNDLES_CARGO);
			String key = toBundleKey(name, locale);
			map.put(key, ret);
			onResourceBundleFetched(name, locale, ret);
		}
	}

	/**
	 * Command that converts a basename and locale of a resource into a key.
	 * @param name - basename of the resource bundle
	 * @param locale - locale of the resource bundle
	 * @return - hashcoded key representation of the resource bundle specifier
	 */
	static public String toBundleKey(String name, LocaleInfo locale) {
		String hcLocale = locale == null ? "": locale.hashCode() + ""; 
		String hc = name.hashCode() + "::" + hcLocale;
		return hc;
	}
}
